﻿using Bouyei.Geo.Converters;
using Bouyei.Geo.Geometries;
using Bouyei.Geo.GeoParsers.Wkb;
using System;
using System.Collections.Generic;

namespace Bouyei.Geo.GeoParsers.PostGIS
{
    public class GeometryWriter:IDisposable
    {
        private BitExtensions bitExt = null;
        public int SRID { get; set; } = -1;

        private Geometry geometry = null;

        public GeometryWriter(Geometry geometry,
            ByteOrder byteOrder=ByteOrder.LittleEndian)
        {
            this.geometry = geometry;
            bitExt = new BitExtensions(byteOrder);
            SRID = geometry.SRID;
        }

        public void Dispose()
        {
            geometry = null;
        }

        public unsafe byte[] Writer()
        {
            int length = 5;
            if (SRID>0)
            {
                length += 4;
            }

            switch (geometry.GeometryType)
            {
                case GeoType.POINT:
                    return WritePoint(length);
                case GeoType.LINESTRING:
                    return WriteLineString(length);
                case GeoType.POLYGON:
                    return WritePolygon(length);
                case GeoType.MULTIPOINT:
                    return WriteMultiPoint(length);
                case GeoType.MULTILINESTRING:
                    return WriteMultiLineString(length);
                case GeoType.MULTIPOLYGON:
                    return WriteMultiPolygon(length); 
                default:
                    throw new Exception("not supported geometry type");
            }
        }

        private unsafe byte[] WritePoint(int head)
        {
            byte[] buffer = new byte[head + 16];

            fixed (byte* ptr = &buffer[0])
            {
                int flag = WriteHeader(ptr, SRID, geometry.GeometryType);
                var seq = geometry.GetSequence(0, 0)[0];

                bitExt.WriteTo(ptr + flag, bitExt.GetBytes(seq.X));
                bitExt.WriteTo(ptr + flag + 8, bitExt.GetBytes(seq.Y));
            }
            return buffer;
        }

        private unsafe byte[] WriteLineString(int head)
        {
            int pCount = geometry.GetSequence(0, 0).Count;
            int size = head + 4+16 * pCount;//geotype+count+xy[][]
            byte[] buffer = new byte[size];

            fixed (byte* ptr = &buffer[0])
            {
                int flag = WriteHeader(ptr, SRID, GeoType.LINESTRING);
                WkbLineString.ToWriter(geometry, ptr + flag);
            }
            return buffer;
        }

        private unsafe byte[] WritePolygon(int head)
        {
            var seqs = geometry.GetGemoetry(0);
            int size = head+4;//geotype+count

            for (int i = 0; i < seqs.Count; ++i)
            {
                size += (seqs[i].Count * 16)+4;
            }
            byte[] buffer = new byte[size];

            fixed (byte* ptr = &buffer[0])
            {
                int flag = WriteHeader(ptr, SRID, GeoType.POLYGON);
                WkbPolygon.ToWriter(seqs, ptr + flag);
            }
            return buffer;
        }

        private unsafe byte[] WriteMultiPoint(int head)
        {
            var seqs = geometry.GetSequence(0, 0);
            int pCount = seqs.Count;
            int size = head + 4 + 21 * pCount;//head+count+(endian+count+xy)*count

            byte[] buffer = new byte[size];

            fixed (byte* ptr = &buffer[0])
            {
                int flag = WriteHeader(ptr, SRID, GeoType.MULTIPOINT);
                //count
                bitExt.WriteTo(ptr + flag, pCount);
                flag += 4;

                //items point
                for (int i = 0; i < pCount; ++i)
                {
                    bitExt.WriteTo(ptr + flag, (byte)bitExt.ByteOrder);
                    flag += 1;
                    bitExt.WriteTo(ptr + flag, (int)1);
                    flag += 4;

                    WkbPoint.ToWriter(seqs[i], ptr + flag);
                    flag += 16;
                }
            }
            return buffer;
        }

        private unsafe byte[] WriteMultiLineString(int head)
        {
            var seqs = geometry.GetGemoetry(0);
            int size = head + 4;

            for (int i = 0; i < seqs.Count; ++i)
            {
                size += 9;
                for (int j = 0; j < seqs[i].Count; ++j)
                    size += 16;
            }
            byte[] buffer = new byte[size];

            fixed (byte* ptr = &buffer[0])
            {
                int flag = WriteHeader(ptr, SRID, GeoType.MULTILINESTRING);
                bitExt.WriteTo(ptr + flag, seqs.Count);
                flag += 4;

                for (int i = 0; i < seqs.Count; ++i)
                {
                    bitExt.WriteTo(ptr + flag, (byte)bitExt.ByteOrder);
                    flag += 1;
                    bitExt.WriteTo(ptr + flag, (int)GeoType.LINESTRING);//geometry type
                    flag += 4;
                    int offset=WkbLineString.ToWriter(seqs[i], ptr + flag);
                    flag += offset;
                }
            }
            return buffer;
        }

        private unsafe byte[] WriteMultiPolygon(int head)
        {  
            int size = head + 4;
            for (int k = 0; k < geometry.GeometryCount; ++k)
            {
                var seqs = geometry.GetGemoetry(k);
                size += 9;

                for (int i = 0; i < seqs.Count; ++i)
                {
                    size += (seqs[i].Count * 16)+4;
                }
            }
 
            byte[] buffer = new byte[size];

            fixed (byte* ptr = &buffer[0])
            {
                int flag = WriteHeader(ptr, SRID, GeoType.MULTIPOLYGON);
                bitExt.WriteTo(ptr + flag, geometry.GeometryCount);
                flag += 4;

                for (int i = 0; i < geometry.GeometryCount; ++i)
                {
                    bitExt.WriteTo(ptr + flag, (byte)bitExt.ByteOrder);
                    flag += 1;
                    bitExt.WriteTo(ptr + flag, (int)GeoType.POLYGON);
                    flag += 4;
                    int offset= WkbPolygon.ToWriter(geometry.GetGemoetry(i), ptr + flag);
                    flag += offset;
                }
            }
            return buffer;
        }

        private unsafe int WriteHeader(byte* ptr, int srid, GeoType geometryType)
        {
            int flag = 1;
            *ptr = (byte)(bitExt.ByteOrder);

            var typeword = (uint)geometryType;

            if (geometry.Ordinates.HasFlag(Ordinates.Z))
            {
                typeword |= 0x80000000;
            }

            if (geometry.Ordinates.HasFlag(Ordinates.M))
            {
                typeword |= 0x40000000;
            }

            if (srid != -1)
            {
                typeword |= 0x20000000;
            }
            bitExt.WriteTo(ptr + flag, typeword);
            flag += 4;

            if (srid>0)
            {
                bitExt.WriteTo(ptr + flag, srid);
                flag += 4;
            }

            return flag;
        }
    }
}
